객체지향 설계 실전

객체지향 설계 실전

도메인 책임 분리, 일급 컬렉션, 예외 계층 설계

책임 분리 원칙

검증(Validation) 위치

public class Car {
    private final String name;

    public Car(String name) {
        validate(name);  // 생성 시점 검증
        this.name = name;
    }

    private void validate(String name) {
        if (name.length() > 5) {
            throw new CarRacingException(ErrorCode.INVALID_CAR_NAME);
        }
    }
}

일급 컬렉션 (First-Class Collection)

컬렉션을 래핑한 도메인 클래스 — 관련 행동을 한 곳에 모음

// Before: 컨트롤러/서비스가 직접 List<Car> 조작
List<Car> cars = new ArrayList<>();

// After: Cars 일급 컬렉션
public class Cars {
    private final List<Car> cars;

    public Cars(List<Car> cars) {
        this.cars = new ArrayList<>(cars);
    }

    public void race() {
        cars.forEachmoveForward;
    }

    public List<Car> getWinners() {
        int maxPosition = getMaxPosition();
        return cars.stream()
            .filter(car -> car.isAt(maxPosition))
            .collect(toList());
    }
}

장점


예외 계층 설계

ErrorCode Enum으로 에러 메시지 관리

public enum ErrorCode {
    INVALID_CAR_NAME("[ERROR] 자동차 이름은 5자 이하여야 합니다."),
    INVALID_ATTEMPT_COUNT("[ERROR] 시도 횟수는 양수여야 합니다."),
    DUPLICATE_CAR_NAME("[ERROR] 자동차 이름은 중복될 수 없습니다.");

    private final String message;

    ErrorCode(String message) {
        this.message = message;
    }
}

도메인 예외 상속

// 도메인 전용 예외 → IllegalArgumentException 상속
public class CarRacingException extends IllegalArgumentException {
    public CarRacingException(ErrorCode errorCode) {
        super(errorCode.getMessage());
    }
}

장점: 외부에서 IllegalArgumentException으로 캐치 가능하면서 도메인 의미 명확


자동차 경주 최종 설계

CarRacingApplication
  └─ CarRacingController
       ├─ InputView        # 입력 전담
       ├─ OutputView       # 출력 전담
       ├─ RacingService    # 경주 진행 (Cars 조작)
       └─ WinnerService    # 우승자 선별
             └─ Cars (일급 컬렉션)
                  └─ Car   # 이름, 위치, 이동 로직
클래스 책임
InputView 입력만, 검증 없음
RacingService 라운드 진행, Cars 상태 변경
WinnerService 우승자 판별
Cars 컬렉션 조작, 최대값 계산
Car 이름 검증, 이동 결정

설계 고민 포인트

Q. 파싱과 검증을 어디서?

InputView → raw String → Parser → 검증된 값 → Service

Q. try-catch를 Controller에서만?

// Controller에서 일괄 처리
while (true) {
    try {
        String input = inputView.readCarNames();
        cars = new Cars(carNames);  // 여기서 검증 예외 발생 가능
        break;
    } catch (IllegalArgumentException e) {
        outputView.printError(e.getMessage());
    }
}

관련 개념